package com.yuanzi.base.lock;

import java.util.List;
import java.util.Objects;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

/**
 * Redis分布式锁
 * @author chengshx
 * @date 2018年3月8日
 */
@Component
public class RedisDistributedLock {
	
	private final static String LOCK_PREFIX = "lock:";
	// 默认超时时间(ms)
	private final static long DEFAULT_TIMEOUT = 5000;
	// 默认等待锁的时间(ms)
	private final static long DEFAULT_ACQUIRE_TIMEOUT = 500;
	
	@Autowired
	private JedisPool jedisPool;
	
	public RedisDistributedLock() {
	}

	/**
	 * 加锁
	 * @author chengshx
	 * @param lockName
	 * @param timeout
	 * @param acquireTimeout
	 * @return
	 */
	public String lock(String lockName, long timeout, long acquireTimeout) {
		// 设置默认时间
		if(timeout <= 0) timeout = DEFAULT_TIMEOUT;
		if(acquireTimeout <= 0) acquireTimeout = DEFAULT_ACQUIRE_TIMEOUT;
		Jedis jedis = null;
		try {
			// 在允许获取锁的时间内获取锁
			long endTime = System.currentTimeMillis() + acquireTimeout + 1;
			String key = LOCK_PREFIX + lockName;
			jedis = jedisPool.getResource();
			while(System.currentTimeMillis() < endTime) {
				String value = UUID.randomUUID().toString();
				// 成功上锁
				if(jedis.setnx(key, value) == 1) {
					// 设置锁的超时时间，防止死锁
					jedis.pexpire(key, timeout);
					return value;
				}
				try {
					// 100ms后再获取锁
					Thread.sleep(100);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
			return null;
		} finally {
			if(jedis != null) {
				jedis.close();
			}
		}
	}

	/**
	 * 释放锁
	 * @author chengshx
	 * @param lockName
	 * @param value
	 */
	public void unlock(String lockName, String value) {
		Jedis jedis = null;
		try {
			String key = LOCK_PREFIX + lockName;
			jedis = jedisPool.getResource();
			while(true) {
				jedis.watch(lockName);
				// 存在锁则释放
				if(Objects.equals(value, jedis.get(key))) {
					Transaction transaction = jedis.multi();
					transaction.del(key);
					List<Object> result = transaction.exec();
					if(result == null) {
						continue;
					}
				}
				jedis.unwatch();
				break;
			}
		} finally {
			if(jedis != null) {
				jedis.close();
			}
		}
	}

}
